﻿namespace OverStore.Configuration.Storage
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using OverStore.Configuration.Validation;
    using OverStore.Exceptions.Configuration;
    using OverStore.Runtime.OverStoreContext;
    using OverStore.Runtime.Storage;

    /// <summary>
    /// Contains configuration parameters and methods allows to create instances of <see cref="AdoNetCommand"/>.
    /// </summary>
    public sealed class AdoNetCommandConfiguration : AdoNetCommandBaseConfiguration, ISupportValidation
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AdoNetCommandConfiguration"/> class.
        /// </summary>
        public AdoNetCommandConfiguration()
        {
            CommandType = CommandType.Text;
            ResultType = AdoCommandResultType.OutputParameters;
        }

        /// <summary>
        /// Gets or sets the SQL instruction executes agianst data storage.
        /// </summary>
        /// <result>The SQL instruction.</result>
        public string Sql { get; set; }

        /// <summary>
        /// Gets or sets the type of the <see cref="T:System.Data.IDbCommand"/> command executes in order to execute AdoNetC.
        /// </summary>
        /// <result>The type of the command.</result>
        public CommandType CommandType { get; set; }

        /// <summary>
        /// Gets or sets the command parameters.
        /// </summary>
        /// <result>The command parameters.</result>
        public IEnumerable<AdoNetParameter> Parameters { get; set; }

        /// <summary>
        /// Gets or sets the way how the command result is extracted.
        /// </summary>
        public AdoCommandResultType ResultType { get; set; }

        /// <summary>
        /// Perform component validation. Method should validate only
        /// component configuration itself, not sub components.
        /// If configuration is valid, null result should returns.
        /// </summary>
        /// <returns>
        ///     <see cref="T:OverStore.Configuration.ValidationResults"/> describes errors found in component configuration.
        /// </returns>
        public ValidationResult Validate()
        {
            return new ValidationResult(this)
                .AddError(String.IsNullOrEmpty(Sql), "SQL Instruction is missing for command.");
        }

        /// <summary>
        /// When overriden in derived classes, creates new instance of class inherited from <see cref="AdoNetCommandBase"/>.
        /// </summary>
        public override AdoNetCommandBase CreateCommand(IOverStoreContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (String.IsNullOrEmpty(Sql))
                throw this.ConfigurationException("Sql instruction is missing.");
            if (Parameters == null)
                Parameters = Enumerable.Empty<AdoNetParameter>();
            if (Parameters.Any(p => String.IsNullOrEmpty(p.Name)))
                throw this.ConfigurationException("There is parameter with empty name in Parameters collection.");
            return new AdoNetCommand(Sql, CommandType, Parameters, ResultType, context.Logger);
        }


        #region Command Building methods

        /// <summary>
        /// Adds the specified parameter to parameters collection.
        /// </summary>
        /// <param name="parameter">The parameter to add in parameters collection.</param>
        /// <returns>The <see cref="AdoNetCommandConfiguration"/> instance with added parameter.</returns>
        public AdoNetCommandConfiguration WithParameter(AdoNetParameter parameter)
        {
            if (parameter == null)
                throw new ArgumentNullException("parameter");
            Parameters = (Parameters ?? Enumerable.Empty<AdoNetParameter>()).Concat(new[] { parameter }).ToList();
            return this;
        }

        /// <summary>
        /// Configures the command to execute specified text as SQL statement.
        /// </summary>
        public AdoNetCommandConfiguration ExecSQL(string sqlText)
        {
            if (String.IsNullOrEmpty(sqlText))
                throw new ArgumentNullException("sqlText");
            Sql = sqlText;
            CommandType = CommandType.Text;
            return this;
        }

        /// <summary>
        /// Configures the command to run stored procedure with the specified name.
        /// </summary>
        public AdoNetCommandConfiguration RunStoredProcedure(string storedProcedureName)
        {
            if (String.IsNullOrEmpty(storedProcedureName))
                throw new ArgumentNullException("storedProcedureName");
            Sql = storedProcedureName;
            CommandType = CommandType.StoredProcedure;
            return this;
        }

        /// <summary>
        /// Adds nullable input parameter.
        /// </summary>
        /// <param name="name">The name of parameter.</param>
        /// <param name="type">The type of parameter.</param>
        /// <returns>The new instance of <see cref="AdoNetParameter"/> class.</returns>
        public AdoNetCommandConfiguration NullableInputParameter(string name, DbType type)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            var p = new AdoNetParameter
            {
                AllowNull = true,
                DataType = type,
                Direction = ParameterDirection.Input,
                Name = name
            };
            return WithParameter(p);
        }

        /// <summary>
        /// Adds nullable input parameter.
        /// </summary>
        /// <param name="name">The name of parameter.</param>
        /// <param name="type">The type of parameter.</param>
        /// <param name="errorMessage">String used as message for exception that is thrown if parameter value is missing or invalid.</param>
        /// <returns>The new instance of <see cref="AdoNetParameter"/> class.</returns>
        public AdoNetCommandConfiguration NullableInputParameter(string name, DbType type, string errorMessage)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            var p = new AdoNetParameter
            {
                AllowNull = true,
                DataType = type,
                Direction = ParameterDirection.Input,
                Name = name,
                ErrorMessage = errorMessage
            };
            return WithParameter(p);
        }

        /// <summary>
        /// Adds non-nullable input parameter.
        /// </summary>
        /// <param name="name">The name of parameter.</param>
        /// <param name="type">The type of parameter.</param>
        /// <returns>The new instance of <see cref="AdoNetParameter"/> class.</returns>
        public AdoNetCommandConfiguration InputParameter(string name, DbType type)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            return WithParameter(new AdoNetParameter
                                        {
                                            AllowNull = false,
                                            DataType = type,
                                            Direction = ParameterDirection.Input,
                                            Name = name
                                        });
        }

        /// <summary>
        /// Adds non-nullable input parameter.
        /// </summary>
        /// <param name="name">The name of parameter.</param>
        /// <param name="type">The type of parameter.</param>
        /// <param name="errorMessage">String used as message for exception that is thrown if parameter value is missing or invalid.</param>
        /// <returns>The new instance of <see cref="AdoNetParameter"/> class.</returns>
        public AdoNetCommandConfiguration InputParameter(string name, DbType type, string errorMessage)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            return WithParameter(new AdoNetParameter
            {
                AllowNull = false,
                DataType = type,
                Direction = ParameterDirection.Input,
                Name = name,
                ErrorMessage = errorMessage
            });
        }

        /// <summary>
        /// Adds nullable output parameter.
        /// </summary>
        /// <param name="name">The name of parameter.</param>
        /// <param name="type">The type of parameter.</param>
        /// <returns>The new instance of <see cref="AdoNetParameter"/> class.</returns>
        public AdoNetCommandConfiguration NullableOutputParameter(string name, DbType type)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            return WithParameter(new AdoNetParameter
                                    {
                                        AllowNull = true,
                                        DataType = type,
                                        Direction = ParameterDirection.Output,
                                        Name = name
                                    });
        }

        /// <summary>
        /// Adds nullable output parameter.
        /// </summary>
        /// <param name="name">The name of parameter.</param>
        /// <param name="type">The type of parameter.</param>
        /// <param name="errorMessage">String used as message for exception that is thrown if parameter value is missing or invalid.</param>
        /// <returns>The new instance of <see cref="AdoNetParameter"/> class.</returns>
        public AdoNetCommandConfiguration NullableOutputParameter(string name, DbType type, string errorMessage)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            return WithParameter(new AdoNetParameter
            {
                AllowNull = true,
                DataType = type,
                Direction = ParameterDirection.Output,
                Name = name,
                ErrorMessage = errorMessage
            });
        }

        /// <summary>
        /// Adds non-nullable output parameter.
        /// </summary>
        /// <param name="name">The name of parameter.</param>
        /// <param name="type">The type of parameter.</param>
        /// <returns>The new instance of <see cref="AdoNetParameter"/> class.</returns>
        public AdoNetCommandConfiguration OutputParameter(string name, DbType type)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            return WithParameter(new AdoNetParameter
                                        {
                                            AllowNull = false,
                                            DataType = type,
                                            Direction = ParameterDirection.Output,
                                            Name = name
                                        });
        }

        /// <summary>
        /// Adds non-nullable output parameter.
        /// </summary>
        /// <param name="name">The name of parameter.</param>
        /// <param name="type">The type of parameter.</param>
        /// <param name="errorMessage">String used as message for exception that is thrown if parameter value is missing or invalid.</param>
        /// <returns>The new instance of <see cref="AdoNetParameter"/> class.</returns>
        public AdoNetCommandConfiguration OutputParameter(string name, DbType type, string errorMessage)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            return WithParameter(new AdoNetParameter
            {
                AllowNull = false,
                DataType = type,
                Direction = ParameterDirection.Output,
                Name = name,
                ErrorMessage = errorMessage
            });
        }

        #endregion
    }
}
